package org.ldx.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.ldx.lotterysystem.common.exception.ServiceException;
import org.ldx.lotterysystem.common.utils.JacksonUtil;
import org.ldx.lotterysystem.common.utils.MailUtil;
import org.ldx.lotterysystem.controller.param.DrawPrizeParam;
import org.ldx.lotterysystem.dao.dataobject.ActivityPrizeDo;
import org.ldx.lotterysystem.dao.dataobject.WinningRecordDO;
import org.ldx.lotterysystem.dao.mapper.ActivityPrizeMapper;
import org.ldx.lotterysystem.dao.mapper.WinnerRecordMapper;
import org.ldx.lotterysystem.service.DrawPrizeService;
import org.ldx.lotterysystem.service.activitystatus.ActivityStatusManager;
import org.ldx.lotterysystem.service.dto.ConvertActivityStatusDTO;
import org.ldx.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import org.ldx.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import org.ldx.lotterysystem.service.enums.ActivityStatusEnum;
import org.ldx.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.ldx.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;
@Slf4j
@Component
@RabbitListener(queues = QUEUE_NAME) //监听队列：QUEUE_NAME
public class MqReceiver {
    @Resource
    private ActivityStatusManager activityStatusManager;
    @Resource
    private DrawPrizeService drawPrizeService;
    @Resource
    private MailUtil mailUtil;
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Resource
    private ActivityPrizeMapper activityPrizeMapper;
    @Resource
    private WinnerRecordMapper winnerRecordMapper;
    @RabbitHandler
    public void process( Map<String,String> map){
        //接收参数
        log.info("接收到MQ生成者参数:map:{}", JacksonUtil.writeValueAsString(map));
        String data = map.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(data, DrawPrizeParam.class);
        //处理抽奖流程
        try {

            //检验抽奖活动是否有效
            //参数传两个一样的 会导致parma1的状态转换因为para2抛出活动完成的异常而回滚回去
            if(!drawPrizeService.checkDrawPrizeParam(param))
            {
                return;
            }
            //状态扭转处理
            statusConvert(param);
            //保存中奖者信息
            List<WinningRecordDO> winningRecordDOList =
                    drawPrizeService.saveWinnerRecords(param);
            //通知中奖者（邮箱通知）
            syncExecute(winningRecordDOList);
        }catch (ServiceException e){
            log.error("处理MQ消息异常{}:{}",e.getCode(),e.getMessage(),e);
            rollback(param);
            //发生异常，抛出异常，回滚
            throw e;
        }
        catch (Exception e){
            log.error("处理MQ消息异常",e);
            //回滚
            rollback(param);
            //发生异常，抛出异常，消息重试
            throw e;
        }



    }

    /**
     * 处理抽奖异常的回滚行为：恢复之前的库表状态
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        // 1、回滚状态：活动、奖品、人员
        // 状态是否需要回滚
        if (!statusNeedRollback(param)) {
            // 不需要：return
            return;
        }
        // 需要回滚: 回滚
        rollbackStatus(param);

        // 2、回滚中奖者名单
        // 是否需要回滚
        if (!winnerNeedRollback(param)) {
            // 不需要：return
            return;
        }
        // 需要: 回滚
        rollbackWinner(param);
    }

    /**
     * 删除中奖者
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(),param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        //获取活动奖品的中奖者个数
        int count = winnerRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        return count>0;

    }

    /**
     * 恢复相关状态
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(param.getWinnerList().stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);


        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    /**
     * 判断是否需要回滚
     * @param param
     * @return
     */
    private boolean statusNeedRollback(DrawPrizeParam param) {
        //由于在扭转状态时保证了事物的一致性
        //人员和奖品状态只判断其一即可
        //这里只判断奖品状态以确定其他
        ActivityPrizeDo activityPrizeDo = activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        if(activityPrizeDo.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETE.name())){
            return true;
        }
        return false;

    }

    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        // 通过线程池 threadPoolTaskExecutor
        // 扩展：加入策略模式或者其他设计模式来完成后续的异步操作
        // 短信通知
       // threadPoolTaskExecutor.execute(()->sendMessage(winningRecordDOList));
        // 邮件通知
        threadPoolTaskExecutor.execute(()->sendMail(winningRecordDOList));
    }

    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空，不用发邮件！");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            // Hi,胡一博。恭喜你在抽奖活动活动中获得二等奖:吹风机。获奖奖时间为18:18:44,请尽快领取您的奖励
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领 取您的奖励！";
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知", context);
        }
    }


    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO statusDTO = new ConvertActivityStatusDTO();
        statusDTO.setActivityId(param.getActivityId());
        statusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETE);
        statusDTO.setPrizeId(param.getPrizeId());
        statusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETE);
        statusDTO.setUserIds(param.getWinnerList().stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        statusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETE);

        activityStatusManager.handleEvent(statusDTO);

    }

    //  private void statusConvert(DrawPrizeParam param) {
        // 问题：
//        // 1、活动状态扭转有依赖性，导致代码维护性差
//        // 2、状态扭转条件可能会扩展，当前写法，扩展性差，维护性差
//        // 3、代码的灵活性、扩展性、维护性极差
//        // 解决方案：设计模式（责任链设计模式、策略模式）


        //扭转奖品状态
        //查询活动关联的奖品
        //判断奖品状态
        //扭转

        //扭转人员状态
        //查询人员关联的奖品
        //判断人员状态
        //扭转

        //扭转活动状态（所有奖品抽完再进行）
        //查询活动
        //判断活动状态
        //扭转

        //更新redis缓存
    //}
}
